//
//  SAStageObjectRenderer.m
//  GameFrame
//
//  Created by Hiroshi Nomura on 2013/11/11.
//  Copyright (c) 2013年 Hiroshi Nomura. All rights reserved.
//

#import "SAStageObjectRenderer.h"
#import "SAStageObject.h"
#import "matrixUtil.h"

struct _ShaderProgram
{
	GLuint	program;
	GLint*	vtxUniformArray;
	GLsizei vtxUniformArrayCount;
	GLint*	frgUniformArray;
	GLsizei frgUniformArrayCount;
};

//Rendering Context
static const CFIndex PROGRAM_ARRAY_MAXSIZE = 4;
CFMutableArrayRef m_cfProgramArray;
EAGLContext* m_context;

//View Setting
GLuint m_viewWidth;
GLuint m_viewHeight;

//GL Uniform Value
GLint m_uniformMvpIdx;
GLint m_uniformGlobalMatrix;
GLint m_uniformInverseMatrix;

//GL Buffer
GLuint m_defaultFrameBuffer;
GLuint m_colorRenderbuffer;
GLuint m_depthRenderbuffer;

@interface SAStageObjectRenderer()
-(BOOL) _allocateRenderBuffer:(id<EAGLDrawable>)drawable;
-(BOOL) _compileShader:(GLuint*)shader
                  type:(GLenum)type
        sourceFilePath:(NSString*)path
               program:(GLuint) prgName
            versionNum:(GLuint) version
        versionStrSize:(const GLsizei)size;

-(BOOL) _linkProgram:(GLuint)prog;
-(BOOL) _validateProgram:(GLuint)prog;
-(void) _useShaderProgram:(ShaderProgram*)shaderProgram;

-(void) _destroyFBO:(GLuint) fboName;
-(void) _destroyFBOAttachment:(GLenum)attachment;
@end


@implementation SAStageObjectRenderer

- (id) initWithContext:(EAGLContext*)context andDrawable:(id<EAGLDrawable>)drawable
{
	if((self = [super init]))
	{
		m_context = context;
		
		glGenFramebuffers(1, &m_defaultFrameBuffer);
		glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFrameBuffer);
		if ([self _allocateRenderBuffer:drawable]) {
            glEnable(GL_DEPTH_TEST);
            glEnable(GL_CULL_FACE);
            glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
            m_cfProgramArray = CFArrayCreateMutable(kCFAllocatorDefault, PROGRAM_ARRAY_MAXSIZE, NULL);
            
		}else{
            [self _destroyFBO:m_defaultFrameBuffer];
        }
	}
	return self;
}

- (GLuint) buildShaderProgramWithVertexShader:(NSString*)vpath ofVType:(NSString*)vtype
                               FragmentShader:(NSString*)fpath ofFType:(NSString*)ftype
                                   withNormal:(BOOL)hasNormal withTexture:(BOOL)hasTexture withSkeleton:(BOOL)hasSkeleton
{
	
    //Read Shader Source String
	NSString* vertexSourceFilePathName			= [[NSBundle mainBundle] pathForResource:vpath ofType:vtype];
	NSString* fragmentSourcefilePathName			= [[NSBundle mainBundle] pathForResource:fpath ofType:ftype];
	
    //Create Shader Program
	GLuint prgName = glCreateProgram();
	if (prgName == 0)
		return 0;
	
    //Attach Shader Attribute
	glBindAttribLocation(prgName, VERTEX_ATTRIB_IDX_VERTEX, "inPosition");
	if (hasNormal){
		glBindAttribLocation(prgName, VERTEX_ATTRIB_IDX_NORMAL, "inNormal");
	}
	if (hasTexture)
		glBindAttribLocation(prgName, VERTEX_ATTRIB_IDX_TEXTURECOORD0, "inTexcoord");
	
	if (hasSkeleton){
		glBindAttribLocation(prgName, VERTEX_ATTRIB_IDX_WEIGHTMAP, "inWeight");
		glBindAttribLocation(prgName, VERTEX_ATTRIB_IDX_JOINTIDX, "inJointIdx");
	}
	
	float glLanguageVersion;
	sscanf((char *)glGetString(GL_SHADING_LANGUAGE_VERSION), "OpenGL ES GLSL ES %f", &glLanguageVersion);
	GLuint version	= 100 * glLanguageVersion;
	const GLsizei versionStringSize = sizeof("#version 123\n");
	
    // Compile vertex and fragment shaders
    GLuint vertShader, fragShader;
	[self _compileShader:&vertShader type:GL_VERTEX_SHADER sourceFilePath:vertexSourceFilePathName
							   program:prgName versionNum:version versionStrSize:versionStringSize];
    
	[self _compileShader:&fragShader type:GL_FRAGMENT_SHADER sourceFilePath:fragmentSourcefilePathName
                                program:prgName versionNum:version versionStrSize:versionStringSize];
    
    // Attach vertex and fragment shaders to program.
    glAttachShader(prgName, vertShader);
    glAttachShader(prgName, fragShader);

    // Link program
    if (![self _linkProgram:prgName]) {
		NSLog(@"Failed to link program");
		return 0;
    }
    
    // Validate program
    if (![self _validateProgram:prgName]) {
		NSLog(@"Failed to validate program");
		return 0;
    }
    
    // Release vertex and fragment shaders.
    if (vertShader) {
        glDetachShader(prgName, vertShader);
        glDeleteShader(vertShader);
    }
    if (fragShader) {
        glDetachShader(prgName, fragShader);
        glDeleteShader(fragShader);
    }
	glUseProgram(prgName);
    
    return prgName;
}

- (ShaderProgram*) registerShaderProgram:(GLuint)program
			  withVertexUniformNameArray:(NSArray*)vtxUniformArray
			 andFragmentUniformNameArray:(NSArray*)frgUniformArray
{
	struct _ShaderProgram *opShaderProgram = NULL;
	opShaderProgram = (struct _ShaderProgram *)malloc(sizeof(struct _ShaderProgram));
	if (opShaderProgram == NULL) return NULL;
	
	opShaderProgram->program				= program;
	
	opShaderProgram->vtxUniformArrayCount	= [vtxUniformArray count];
	opShaderProgram->vtxUniformArray		= malloc(sizeof(GLint) * opShaderProgram->vtxUniformArrayCount);
	opShaderProgram->frgUniformArrayCount	= [frgUniformArray count];
	opShaderProgram->frgUniformArray		= malloc(sizeof(GLint) * opShaderProgram->frgUniformArrayCount);
	
	if (opShaderProgram->vtxUniformArray != NULL){
		for (int i=0; i<opShaderProgram->vtxUniformArrayCount; i++) {
			NSString* valueName = [vtxUniformArray objectAtIndex:i];
			opShaderProgram->vtxUniformArray[i] = glGetUniformLocation(opShaderProgram->program,
                                                                       [valueName cStringUsingEncoding:NSASCIIStringEncoding]);
		}
		
		if(opShaderProgram->vtxUniformArray[0] < 0){
			NSLog(@"No modelViewProjectionMatrix in character shader");
			[self destroyProgram:(ShaderProgram*)opShaderProgram];
			return NULL;
		}
	}
	
	if (opShaderProgram->frgUniformArray != NULL) {
		for (int i=0; i<opShaderProgram->frgUniformArrayCount; i++) {
			NSString* valueName = [frgUniformArray objectAtIndex:i];
			opShaderProgram->frgUniformArray[i] = glGetUniformLocation(opShaderProgram->program,
																	   [valueName cStringUsingEncoding:NSASCIIStringEncoding]);
		}
	}
	
	CFArrayAppendValue(m_cfProgramArray, opShaderProgram);
    
	[self _useShaderProgram:(ShaderProgram*)opShaderProgram];
	
	return (ShaderProgram*)opShaderProgram;
}

-(void)destroyProgram:(ShaderProgram*) shaderProgram
{
	GLsizei shaderNum;
	GLsizei shaderCount;
	
	struct _ShaderProgram* opShaderProgram = (struct _ShaderProgram*)shaderProgram;
	glGetProgramiv(opShaderProgram->program, GL_ATTACHED_SHADERS, &shaderCount);
	
	GLuint* shaders = (GLuint*)malloc(shaderCount * sizeof(GLuint));
	glGetAttachedShaders(opShaderProgram->program,
						 shaderCount,
						 &shaderCount,
						 shaders);
	
	for(shaderNum = 0; shaderNum < shaderCount; shaderNum++)
	{
		glDeleteShader(shaders[shaderNum]);
	}
	
	free(shaders);
	
	glDeleteProgram(opShaderProgram->program);
	if (opShaderProgram->vtxUniformArray != NULL) {
		free(opShaderProgram->vtxUniformArray);
		opShaderProgram->vtxUniformArray = NULL;
	}
	
	if (opShaderProgram->frgUniformArray != NULL) {
		free(opShaderProgram->frgUniformArray);
		opShaderProgram->frgUniformArray = NULL;
	}
	
	free(shaderProgram);
	shaderProgram = NULL;
}

- (void) render:(ShaderProgram*)shaderProgram viewMatrix:(Matrix4x4*)viewMatrix objects:(NSArray*)objectArray;
{
	//bindframebuffer and bindrenderbuffer might have to be executed before this method is called.
	glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFrameBuffer);
	[self _useShaderProgram:shaderProgram];

	Matrix4x4 projectionMatrix;
	mtxLoadPerspective(projectionMatrix, 34.708, (float)m_viewWidth / (float)m_viewHeight,5.0,10000);

    Matrix4x4 screenMatrix;
    mtxLoadIdentity(screenMatrix);

	mtxMultiply(screenMatrix, projectionMatrix, viewMatrix);
	
	int arraySize = [objectArray count];
	for (int i = 0; i < arraySize; i++) {
		//struct _ShaderProgram* opShaderProgram = (struct _ShaderProgram*)shaderProgram;
		SAStageObject* object = (SAStageObject*)[objectArray objectAtIndex:i];
		//[object drawObject];
	}
	
	glBindRenderbuffer(GL_RENDERBUFFER, m_colorRenderbuffer);
	[m_context presentRenderbuffer:GL_RENDERBUFFER];
}

- (GLuint)getViewWidth { return m_viewWidth; }
- (GLuint)getViewHeight{ return m_viewHeight;}

//-----------------private method
-(BOOL)_allocateRenderBuffer:(id<EAGLDrawable>)drawable
{
	glBindRenderbuffer(GL_RENDERBUFFER, m_colorRenderbuffer);
	[m_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:drawable];
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorRenderbuffer);
	
	GLint backingWidth;
	GLint backingHeight;
	glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
	glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
	
	glGenRenderbuffers(1, &m_depthRenderbuffer);
	glBindRenderbuffer(GL_RENDERBUFFER, m_depthRenderbuffer);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, backingWidth, backingHeight);
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthRenderbuffer);
	
	if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
	{
		NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
		return NO;
	}
	
	m_viewWidth	= backingWidth;
	m_viewHeight= backingHeight;
	glViewport(0, 0, m_viewWidth, m_viewHeight);
	
	return YES;
}

-(BOOL) _compileShader:(GLuint*)shader
                  type:(GLenum)type
        sourceFilePath:(NSString*)path
               program:(GLuint) prgName
            versionNum:(GLuint) version
        versionStrSize:(const GLsizei)size
{
    GLint status;
    const GLchar *source;
    
    source = (GLchar *)[[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil] UTF8String];
    if (!source) {
        NSLog(@"Failed to load vertex shader");
        return NO;
    }
    
    *shader = glCreateShader(type);
    glShaderSource(*shader, 1, &source, NULL);
    glCompileShader(*shader);
    
#if defined(DEBUG)
    GLint logLength;
    glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetShaderInfoLog(*shader, logLength, &logLength, log);
        NSLog(@"Shader compile log:\n%s", log);
        free(log);
    }
#endif
    
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
    if (status == 0) {
        glDeleteShader(*shader);
        return NO;
    }
    
    return YES;
}

- (BOOL)_linkProgram:(GLuint)prog
{
    GLint status;
    glLinkProgram(prog);
    
#if defined(DEBUG)
    GLint logLength;
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        NSLog(@"Program link log:\n%s", log);
        free(log);
    }
#endif
    
    glGetProgramiv(prog, GL_LINK_STATUS, &status);
    if (status == 0) {
        return NO;
    }
    
    return YES;
}

- (BOOL)_validateProgram:(GLuint)prog
{
    GLint logLength, status;
    
    glValidateProgram(prog);
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        NSLog(@"Program validate log:\n%s", log);
        free(log);
    }
    
    glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
    if (status == 0) {
        return NO;
    }
    
    return YES;
}

- (void)_useShaderProgram:(ShaderProgram*)shaderProgram
{
	struct _ShaderProgram *opShaderProgram = (struct _ShaderProgram *)shaderProgram;
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glUseProgram(opShaderProgram->program);
}

-(void)_destroyFBOAttachment:(GLenum)attachment
{
	GLint param;
    GLuint objName;
	
    glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, attachment,
                                          GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,
                                          &param);
	
    if(GL_RENDERBUFFER == param)
    {
        glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, attachment,
                                              GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
                                              &param);
		
        objName = ((GLuint*)(&param))[0];
        glDeleteRenderbuffers(1, &objName);
    }
    else if(GL_TEXTURE == param)
    {
        
        glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, attachment,
                                              GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
                                              &param);
		
        objName = ((GLuint*)(&param))[0];
        glDeleteTextures(1, &objName);
    }
	
}

-(void)_destroyFBO:(GLuint) fboName
{
	if (fboName == 0) return;
	
	glBindFramebuffer(GL_FRAMEBUFFER, fboName);
	
	GLint colorAttachment;
	GLint maxColorAttachments = 1;
	
	for (colorAttachment = 0; colorAttachment < maxColorAttachments; colorAttachment++)
	{
		//[self destroyFBOAttachment:(GL_COLOR_ATTACHMENT0+colorAttachment)];
	}
	
	//[self destroyFBOAttachment:GL_DEPTH_ATTACHMENT];
	
	glDeleteFramebuffers(1, &fboName);
}

@end
